package main.java.person.fjsp.algorithm.ga.utils;

import main.java.person.fjsp.algorithm.ga.entity.Individual;
import main.java.person.fjsp.algorithm.ts.entity.DisjunctiveGraph;
import main.java.person.fjsp.algorithm.ts.entity.DisjunctiveGraphNode;
import main.java.person.fjsp.common.entity.*;

import java.util.*;
import java.util.stream.IntStream;

/**
 * @author xcy
 */
public class GAUtils {
    private Random random=new Random();


    /**
     * 随机生成一个初始调度计划
     */
    public ScheduleResult getRandomSchedule(DataManager dataManager){
        ArrayList<String> operations = new ArrayList<>(dataManager.getOperations());
        Collections.shuffle(operations);
        //先生成osCode，再根据osCode生成对应的csCode
        int[] osCode = new int[operations.size()];
        for (int i = 0; i < operations.size(); i++) {
            String[] split = operations.get(i).split("-");
            osCode[i] = Integer.parseInt(split[0]);
        }
        int[] csCode = new int[dataManager.getOperationNum()];
        for (int i = 0; i < osCode.length; i++) {
            List<Integer> machines = dataManager.getOperationCodeAndMachineListMap().get(dataManager.getOperations().get(i));
            //随机选择可用机器
            csCode[i] = machines.get(new Random().nextInt(machines.size()));
        }
        //生成一个可行的调度计划
        return getScheduleResult(csCode, osCode, dataManager);
    }






//    /**
//     * 用于将一个个体的编码变成一个调度计划
//     */
//    public ScheduleResult getScheduleResult(int[] msCode, int[] osCode, DataManager dataManager){
//        //对每个工序出现的次数进行计数
//        int[] operationOrderCount=new int[dataManager.getJobNum()];
//        //机器
//        ArrayList<Machine> machines=new ArrayList<>(dataManager.getMachineNum());
//        for (int i = 0; i < dataManager.getMachineNum(); i++) {
//            machines.add(new Machine(i,new ArrayList<>()));
//        }
//        //作业
//        ArrayList<Job> jobs=new ArrayList<>(dataManager.getJobNum());
//        for (int i = 0; i < dataManager.getJobNum(); i++) {
//            jobs.add(new Job(i,new ArrayList<>()));
//        }
//        for (int jobNumber : osCode) {
//            //获得工序编号
//            String operationCode = jobNumber + "-" + operationOrderCount[jobNumber];
//            //该工序选择的机器的编号
//            int csCodeIndex = dataManager.getOperationCodeAndIndexMap().get(operationCode);
//            int machineNumber = msCode[csCodeIndex];
//            //该工序在机器上的处理时间
//            int processTime = dataManager.getOperationProcessTimeMap().get(operationCode + "-" + machineNumber);
//
//            Machine machine = machines.get(machineNumber);
//            Job job = jobs.get(jobNumber);
//            //判断作业紧前工序和机器紧前工序的完工时间哪个最晚
//            int latestCompleteTime = Math.max(machine.getCompleteTime(), job.getCompleteTime());
//            //封装一个工序
//            Operation operation = new Operation(jobNumber, operationOrderCount[jobNumber], operationCode,
//                    latestCompleteTime, latestCompleteTime + processTime,machineNumber,processTime);
//            //更新机器序列和机器的最大完工时间
//            machine.getOperations().add(operation);
//            machine.setCompleteTime(operation.getCompleteTime());
//            //更新作业序列和作业的最大完工时间
//            job.getOperations().add(operation);
//            job.setCompleteTime(operation.getCompleteTime());
//            //作业工序次数计数
//            operationOrderCount[jobNumber]++;
//        }
//        //获得该调度计划的最大完工时间（makespan）
//        int makespan=machines.stream().max(Comparator.comparing(Machine::getCompleteTime)).orElseThrow().getCompleteTime();
//        return new ScheduleResult(machines,jobs,makespan);
//    }

    /**
     * 用于将一个个体的编码变成一个调度计划
     */
    public ScheduleResult getScheduleResult(int[] csCode, int[] osCode, DataManager dataManager){
        //对每个工序出现的次数进行计数
        int[] operationOrderCount=new int[dataManager.getJobNum()];
        //机器
        ArrayList<Machine> machines=new ArrayList<>(dataManager.getMachineNum());
        for (int i = 0; i < dataManager.getMachineNum(); i++) {
            machines.add(new Machine(i,new ArrayList<>()));
        }
        //作业
        ArrayList<Job> jobs=new ArrayList<>(dataManager.getJobNum());
        for (int i = 0; i < dataManager.getJobNum(); i++) {
            jobs.add(new Job(i,new ArrayList<>()));
        }
        for (int jobNumber : osCode) {
            //获得工序编号
            String operationCode = jobNumber + "-" + operationOrderCount[jobNumber];

            //该工序选择的机器的编号
            int csCodeIndex = dataManager.getOperationCodeAndIndexMap().get(operationCode);
            int machineNumber = csCode[csCodeIndex];
            //该工序在机器上的处理时间
            int processTime = dataManager.getOperationProcessTimeMap().get(operationCode + "-" + machineNumber);

            Machine machine = machines.get(machineNumber);
            Job job = jobs.get(jobNumber);
            //判断作业紧前工序和机器紧前工序的完工时间哪个最晚
            int latestCompleteTime = Math.max(machine.getCompleteTime(), job.getCompleteTime());
            //封装一个工序
            Operation operation = new Operation(jobNumber, operationOrderCount[jobNumber], operationCode,
                    latestCompleteTime, latestCompleteTime + processTime,machineNumber,processTime);
            //更新机器序列和机器的最大完工时间
            machine.getOperations().add(operation);
            machine.setCompleteTime(operation.getCompleteTime());
            //更新作业序列和作业的最大完工时间
            job.getOperations().add(operation);
            job.setCompleteTime(operation.getCompleteTime());
            //作业工序次数计数
            operationOrderCount[jobNumber]++;
        }
        //获得该调度计划的最大完工时间（makespan）
        int makespan=machines.stream().max(Comparator.comparing(Machine::getCompleteTime)).orElseThrow().getCompleteTime();
        return new ScheduleResult(machines,jobs,makespan);

    }

    public void checkScheduleResult(ScheduleResult scheduleResult,DataManager dataManager) throws Exception {
        if(scheduleResult==null||scheduleResult.getMachines().isEmpty()||scheduleResult.getJobs().isEmpty()){
            throw new Exception("schedule should be valid");
        }

        for (Job job : scheduleResult.getJobs()) {
            String pre="0";
            for (int i = 0; i < job.getOperations().size(); i++) {
                Operation operation = job.getOperations().get(i);
                String order = operation.getCode().split("-")[1];
                if(i==0){
                    if(!Objects.equals(order, pre)){
                        throw new IllegalStateException("not expecting to arrive here");
                    }
                }else{
                    if(Integer.parseInt(order)<=Integer.parseInt(pre)){
                        throw new IllegalStateException("not expecting to arrive here");
                    }
                    pre=order;
                }
            }
        }
        int count=0;
        for (Machine machine : scheduleResult.getMachines()) {
            count+=machine.getOperations().size();
        }
        if(count!=dataManager.getOperationNum()){
            throw new Exception("operation num error");
        }
    }


    /**
     * 从析取图中解析出cs编码
     * @param graph 析取图
     * @return cs编码
     */
    public int[] parseCsCode(DisjunctiveGraph graph){
        int[] csCode=new int[graph.getNodes().size()-2];
        for (int i = 1; i < graph.getNodes().size()-1; i++) {
            csCode[i-1]=graph.getNodes().get(i).getMachineNum();
        }
        return csCode;
    }

    /**
     * 从析取图中解析出os编码
     * @param graph 析取图
     * @return os编码
     */
    public int[] parseOsCode(DisjunctiveGraph graph){
        int[] osCode=new int[graph.getNodes().size()-2];
        List<DisjunctiveGraphNode> nodes = new ArrayList<>(graph.getNodes().subList(1, graph.getNodes().size() - 1));
        nodes.sort(Comparator.comparingInt(DisjunctiveGraphNode::getStartTime));
        for (int i = 0; i < nodes.size(); i++) {
            osCode[i]=nodes.get(i).getJobNum();
        }
        return osCode;
    }

    /**
     * 生成初始种群
     */
    public ArrayList<Individual> generateInitialPopulation(DataManager dataManager,int initialPopSize) {
        ArrayList<Individual> individuals = new ArrayList<>(initialPopSize);
        ArrayList<String> operations = new ArrayList<>(dataManager.getOperations());
        for (int k = 0; k < initialPopSize; k++) {
            Collections.shuffle(operations);
            //先生成osCode，再根据osCode生成对应的csCode
            int[] osCode = new int[operations.size()];
            for (int i = 0; i < operations.size(); i++) {
                String[] split = operations.get(i).split("-");
                osCode[i] = Integer.parseInt(split[0]);
            }
            int[] csCode = new int[dataManager.getOperationNum()];
            for (int i = 0; i < osCode.length; i++) {
                List<Integer> machines = dataManager.getOperationCodeAndMachineListMap().get(dataManager.getOperations().get(i));
                //随机选择可用机器
                csCode[i] = machines.get(new Random().nextInt(machines.size()));
            }
            individuals.add(new Individual(csCode, osCode));
        }
        return individuals;
    }

    /**
     * 解码
     *
     * @return 个体的适应值
     */
    public double decode(Individual individual, DataManager dataManager) {
        ScheduleResult scheduleResult = getScheduleResult(individual.getCsCode(), individual.getOsCode(), dataManager);
        return scheduleResult.getMakespan();
    }

    /**
     * 选择
     */
    public ArrayList<Individual> selection(ArrayList<Individual> individuals,int eSize,int tNum) throws Exception {
        int selectNum = individuals.size() / 2;
        if (eSize  >= selectNum) {
            System.out.println("eSize:" + eSize);
            System.out.println("selectNum/2:" + selectNum/2);
            throw new Exception("eSize could not be more than initialPopSize/2");
        }

        HashSet<Individual> individualHashSet = new HashSet<>(individuals);
        ArrayList<Individual> selectIndividuals = new ArrayList<>();
        //先进行精英选择，选出eSize个个体
        for (int i = 0; i < eSize; i++) {
            Individual individual = individuals.stream().max(Comparator.comparing(Individual::getFitness)).orElseThrow();
            selectIndividuals.add(individual);
            individualHashSet.remove(individual);
            individuals = new ArrayList<>(individualHashSet);
        }
        if (tNum > individuals.size() && tNum < 2) {
            //参与锦标赛的个体数量不能超过此时还剩下的个体数量，此外，参与锦标赛的个体不能小于2个
            throw new Exception("error");
        }
        //再进行锦标赛选择，选出tSize个个体
        for (int i = 0; i < selectNum-eSize; i++) {
            Collections.shuffle(individuals);
            Individual bestIndividual = individuals.get(0);
            for (int j = 1; j < tNum; j++) {
                if (bestIndividual.getFitness() > individuals.get(j).getFitness()) {
                    bestIndividual = individuals.get(j);
                }
            }
            //如果一个个体适应值很高，有可能会被重复选到，new出一个新的
            selectIndividuals.add(bestIndividual.clone());
        }
        return selectIndividuals;

    }


    /**
     * 个体交叉
     *
     * @return 交叉后产生的两个子代
     */
    public List<Individual> crossover(Individual parent1, Individual parent2, int jobNum, int operationNum) {
        //两个父代的编码
        int[] p1csCode = parent1.getCsCode(), p2csCode = parent2.getCsCode();
        int[] p1osCode = parent1.getOsCode(), p2osCode = parent2.getOsCode();
        //两个子代的编码
        int[] o1csCode = new int[operationNum], o2csCode = new int[operationNum];
        int[] o1osCode = new int[operationNum], o2osCode = new int[operationNum];

        //产生cs编码的交叉点
        int random1 = random.nextInt(operationNum - 1);
        int random2 = random.nextInt(operationNum - random1) + random1;
//        System.out.println("cs crossPoint:"+random1+","+random2);
        for (int i = 0; i < operationNum; i++) {
            //cs交叉
            if (random1 <= i && i < random2) {
                //第二段，p1给o2，p2给o1
                o1csCode[i] = p2csCode[i];
                o2csCode[i] = p1csCode[i];
            } else {
                //第一段或第三段，p1给o1，p2给o2
                o1csCode[i] = p1csCode[i];
                o2csCode[i] = p2csCode[i];
            }
        }

        //用于完成os编码的准备数据
        //随机将作业划分为两部分
        List<Integer> jobList = new ArrayList<>(IntStream.range(0, jobNum).boxed().toList());
        Collections.shuffle(jobList);
        int randomIndex = random.nextInt(jobList.size() - 1) + 1;
        Set<Integer> jobPartOne = new HashSet<>(jobList.subList(0, randomIndex)), jobPartTwo = new HashSet<>(jobList.subList(randomIndex, jobList.size()));
//        System.out.println("jobPartOne:"+jobPartOne+",jobPartTwo"+jobPartTwo);
        if(random.nextDouble()>0.5){
            pox(operationNum,p1osCode,p2osCode,o1osCode,o2osCode,jobPartOne,jobPartTwo);
        }else {
            jbx(operationNum,p1osCode,p2osCode,o1osCode,o2osCode,jobPartOne,jobPartTwo);
        }



        List<Individual> offsprings = new ArrayList<>(2);
        offsprings.add(new Individual(o1csCode, o1osCode));
        offsprings.add(new Individual(o2csCode, o2osCode));
        return offsprings;
    }

    /**
     * os编码交叉算子——POX
     */
    public void pox(int operationNum,int[] p1osCode,int[] p2osCode,int[] o1osCode,int[] o2osCode,
                     Set<Integer> jobPartOne,Set<Integer> jobPartTwo){

        //第一次遍历完成之后，o1、o2未被使用的位置
        ArrayList<Integer> o1osIndexNotUse = new ArrayList<>(), o2osIndexNotUse = new ArrayList<>();
        for (int i = 0; i < operationNum; i++) {
            //os部分交叉
            if (jobPartOne.contains(p1osCode[i])) {
                o1osCode[i] = p1osCode[i];
            } else {
                o1osIndexNotUse.add(i);
            }
            if (jobPartOne.contains(p2osCode[i])) {
                o2osCode[i] = p2osCode[i];
            } else {
                o2osIndexNotUse.add(i);
            }
        }
        //完成os的交叉
        int index2 = 0, index1 = 0;
        for (int i = 0; i < operationNum; i++) {
            if (jobPartTwo.contains(p1osCode[i])) {
                o2osCode[o2osIndexNotUse.get(index2)] = p1osCode[i];
                index2++;
            }
            if (jobPartTwo.contains(p2osCode[i])) {
                o1osCode[o1osIndexNotUse.get(index1)] = p2osCode[i];
                index1++;
            }
        }
    }

    /**
     * os编码交叉算子——JBX
     */
    public void jbx(int operationNum,int[] p1osCode,int[] p2osCode,int[] o1osCode,int[] o2osCode,
                     Set<Integer> jobPartOne,Set<Integer> jobPartTwo){
        //第一次遍历完成之后，o1、o2未被使用的位置
        ArrayList<Integer> o1osIndexNotUse = new ArrayList<>(), o2osIndexNotUse = new ArrayList<>();
        for (int i = 0; i < operationNum; i++) {
            //os部分交叉
            if (jobPartOne.contains(p1osCode[i])) {
                o1osCode[i] = p1osCode[i];
            } else {
                o1osIndexNotUse.add(i);
            }
            if (jobPartTwo.contains(p2osCode[i])) {
                o2osCode[i] = p2osCode[i];
            } else {
                o2osIndexNotUse.add(i);
            }
        }
        //完成os的交叉
        int index2 = 0, index1 = 0;
        for (int i = 0; i < operationNum; i++) {
            if (jobPartOne.contains(p1osCode[i])) {
                o2osCode[o2osIndexNotUse.get(index2)] = p1osCode[i];
                index2++;
            }
            if (jobPartTwo.contains(p2osCode[i])) {
                o1osCode[o1osIndexNotUse.get(index1)] = p2osCode[i];
                index1++;
            }
        }
    }


    /**
     * 个体变异
     */
    public void mutation(Individual individual, DataManager dataManager) throws Exception {
        int[] csCode = individual.getCsCode();
        int[] osCode = individual.getOsCode();
        //cs编码的变异，依次访问每个元素，以50%概率改变工序的机器选择
        for (int i = 0; i < dataManager.getOperationNum(); i++) {
            if (random.nextDouble() < 0.5) {
                int currentMachine = csCode[i];
                String code = dataManager.getOperations().get(i);
                List<Integer> machines = new ArrayList<>(dataManager.getOperationCodeAndMachineListMap().get(code));
                if (machines.size() == 1) {
                    continue;
                }
                if (machines.removeIf(machineNumber -> machineNumber == currentMachine)) {
                    //随机在去除了当前机器的机器列表中选择一个机器
                    csCode[i] = machines.get(random.nextInt(machines.size()));
                } else {
                    throw new Exception("current machine should be included in machines");
                }
            }
        }
        //os编码的变异，随机交换两道工序
        int random1 = random.nextInt(osCode.length), random2 = random.nextInt(osCode.length);
        int temp = osCode[random1];
        osCode[random1] = osCode[random2];
        osCode[random2] = temp;

    }

    /**
     * 种群交叉与变异
     * @param selectIndividuals 完成选择的种群
     * @param dataManager 数据管理器
     * @return 交叉变异完成后产生的后代
     * @throws Exception 逻辑bug出现异常
     */
    public ArrayList<Individual> popCrossoverAndMutation(ArrayList<Individual> selectIndividuals,
                                                         double crossoverProbability,double mutationProbability,
                                                         DataManager dataManager) throws Exception {
        ArrayList<Individual> offSprings = new ArrayList<>(selectIndividuals.size() * 2);
        for (int j = 0; j < selectIndividuals.size(); j++) {
            //这里是按顺序进行交叉，也可以采用随机选取两个个体进行交叉的方式
            Individual parent1 = selectIndividuals.get(j);
            Individual parent2 = j == selectIndividuals.size() - 1 ? selectIndividuals.get(0) : selectIndividuals.get(j + 1);
            Individual offSpring1;
            Individual offSpring2;
            if (random.nextDouble() < crossoverProbability) {
                List<Individual> crossover = crossover(parent1, parent2, dataManager.getJobNum(), dataManager.getOperationNum());
                offSpring1 = crossover.get(0);
                offSpring2 = crossover.get(1);
            } else {
                offSpring1 = parent1.clone();
                offSpring2 = parent2.clone();
            }
            if (random.nextDouble() < mutationProbability) {
                mutation(offSpring1, dataManager);
            }
            if (random.nextDouble() < mutationProbability) {
                mutation(offSpring2, dataManager);
            }
            offSprings.add(offSpring1);
            offSprings.add(offSpring2);
        }
        return offSprings;
    }

}
